/**
 * 
 */
package se.ricardo.gamesorgs.database.models;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;


import se.ricardo.gamesorgs.api.request.AddGameRequest;
import se.ricardo.gamesorgs.api.request.EditGameRequest;
import se.ricardo.gamesorgs.api.request.GamesOptionsRequest;
import se.ricardo.gamesorgs.database.DBConnector;

/**
 * Model class for usage with the MVC design pattern.<br>
 * Handles the connection to the database through a
 * {@link DBConnector} object, and stores the data for
 * the "Controller" to retrieve it.
 * <br>
 * 
 * @author Ricardo Ocaña Leal
 *
 */
public class GamesModel {
	private DBConnector connector;
	
	/* Lists for all the requested data */
	private List<String> companiesList = new ArrayList<>();
	private Map<String, Integer> systemsList = new TreeMap<>(); 
	private List<GameContainer> gamesList = new ArrayList<>();
	
	/**
	 * Creates an object to {@link DBConnector} and establishes
	 * a connection.<br>
	 * Also gets all the available companies afterwards.
	 * */
	public GamesModel() {
		connector = new DBConnector();
		
		try {
			connector.createConnection();
			fillCompanies();
		}
		catch(SQLException sqle) {
			System.err.println(sqle);
			sqle.printStackTrace();
		}
	}
	
	/**
	 * Called to notify the "model" that the user has requested to
	 * add a new game to the database, or edit an existing one.
	 * <br>
	 * 
	 * @param
	 * request A {@link AddGameRequest} object containing the
	 * required data.
	 * */
	public void addGame(AddGameRequest request) {
		if(request instanceof EditGameRequest) {
			try {
				editGame((EditGameRequest)request);
			}
			catch(SQLException sqle) {
				System.err.println(sqle);
				sqle.printStackTrace();
			}
		}
		else {
			boolean addAllowed = false;
			
			try {
				addBaseGame(
						request.getTitle(),
						request.getSystem(),
						null);
				
				// Set add to true if it managed to store the "base" game.	
				addAllowed = true;
			}
			catch(SQLException sqle) {
				int code = sqle.getErrorCode();
				
				// Check that the error code signifies that
				// the entry already exists in the StoredGames database.
				if(code == 1062) {
					addAllowed = true;
				}
			}
			finally {
				if(addAllowed) {
					try {
						addGame(
								request.getTitle(),
								request.getSystem(),
								request.getCart(),
								request.getInstructions(),
								request.getBox(),
								request.getComment(),
								request.getListType()
								);
					}
					catch(SQLException sqle2) {
						System.err.println(sqle2);
						sqle2.printStackTrace();
					}
				}
			}
		}
	}
	
	/**
	 * Called when the user has changed one of the GUI options.
	 * Updates the Lists containing all the data.
	 * <br>
	 * 
	 * @param
	 * request Contains all the options that the user has chosen
	 * in the GUI.
	 * */
	public void update(GamesOptionsRequest request) {
		try {
			if(request.shouldSystemsUpdate()) {
				updateSystems(request.getCompany(),
						request.getListType());
			}
			
			String system = request.getSystem();
			
			if(system != null && !system.isEmpty()) {
				updateGames(system,
						request.getListType());
			}
		}
		catch(SQLException sqle) {
			System.err.println(sqle);
			sqle.printStackTrace();
		}
	}
	
	/**
	 * Called when the user has closed the application.<br>
	 * Closes the connection to the database.<br>
	 * This is one of several procedures to execute when shutting
	 * down the application.
	 * */
	public void shutdown() {
		try {
			connector.closeConnection();
		}
		catch(SQLException sqle) {
			System.err.println(sqle);
			sqle.printStackTrace();
		}
	}
	
	/**
	 * Called to get all the available companies.
	 * <br>
	 * 
	 * @return
	 * A {@link List} containing all the companies.
	 * */
	public List<String> getCompanies() {
		return companiesList;
	}
	
	/**
	 * Called to get all the available systems belonging
	 * to a specific company, with the amount of stored
	 * titles for each one.
	 * <br>
	 * 
	 * @return
	 * A {@link Map} containing all the systems.
	 * */
	public Map<String, Integer> getSystems() {
		return systemsList;
	}
	
	/**
	 * Called to get all the systems belonging to a specific company.
	 * <br>
	 * 
	 * @param
	 * company String with the name of the comapany.
	 * <br>
	 * 
	 * @return
	 * An object array containing all the systems.
	 * */
	public Object[] getSpecificSystems(String company) {
		List<String> systems = new ArrayList<>();
		
		try {
			ResultSet rs = connector.getCompanySystems(company);
			
			while(rs.next()) {
				systems.add(rs.getString(1));
			}
		}
		catch(SQLException e) {
			System.err.println(e);
			e.printStackTrace();
		}
		
		return systems.toArray();
	}
	
	/**
	 * Called to get all the available games belonging to
	 * a specific system.
	 * <br>
	 * 
	 * @return
	 * A {@link List} containing all the {@link GameContainer} games.
	 * */
	public List<GameContainer> getGames() {
		return gamesList;
	}
	
	/**
	 * Called to update the systems list belonging to a
	 * specific company.
	 * <br>
	 * 
	 * @param
	 * company A string with the name of the company.
	 * @param
	 * listType Integer representing the list.
	 * <br>
	 * 
	 * @throws
	 * SQLException
	 * */
	private void updateSystems(String company,
			int listType) throws SQLException {
		systemsList.clear();
		ResultSet rs = connector.getCompanySystems(company, listType);

		while(rs.next()) {
			systemsList.put(rs.getString(1), rs.getInt(2));
		}
	}
	
	/**
	 * Called to update the games list based on various user selected
	 * options.
	 * <br>
	 * 
	 * @param
	 * system A string with the name of the system that the games
	 * belong to.
	 * @param
	 * listType Integer representing the list.
	 * <br>
	 * 
	 * @throws
	 * SQLException
	 * */
	private void updateGames(String system, int listType) throws SQLException {
		gamesList.clear();
		ResultSet rs = connector.getPersonalGames(system,
				listType);
		
		while(rs.next()) {
			GameContainer tempGame = new GameContainer(
					rs.getInt(1),
					rs.getString(2),
					rs.getString(3),
					rs.getString(4),
					rs.getString(5),
					rs.getString(6),
					rs.getString(7));
			gamesList.add(tempGame);
		}
	}
	
	/**
	 * Called to fetch all available companies from the database,
	 * and fill the companies list with the returned data.
	 * <br>
	 * 
	 * @throws
	 * SQLException
	 * */
	private void fillCompanies() throws SQLException {
		ResultSet rs = connector.getAllCompanies();
		
		while(rs.next()) {
			companiesList.add(rs.getString(1));
		}
	}
	
	/**
	 * Called to add a "base" game in the database.
	 * <br>
	 * 
	 * @param
	 * title String with the name of the game.
	 * @param
	 * system String with the name of the system.
	 * @param
	 * info Optional String with a url to an information webpage.
	 * <br>
	 * 
	 * @throws
	 * SQLException
	 * */
	private void addBaseGame(String title, String system,
			String info) throws SQLException {
		connector.addBaseGame(title, system, info);
	}
	
	/**
	 * Adds a game to the database.
	 * <br>
	 * 
	 * @param
	 * title String with the name of the game.
	 * @param
	 * system String with the name of the system.
	 * @param
	 * cart String with a condition scale value, for the
	 * cartridge/disc.
	 * @param
	 * instructions String with a condition scale value, for the
	 * instructions manual.
	 * @param
	 * box String with a condition scale value, for the box.
	 * @param
	 * comment String with a user comment about the game.
	 * @param
	 * listType Integer to decide which database table to store the game in.
	 * <br>
	 * 
	 * @throws
	 * SQLException
	 * */
	private void addGame(String title, String system,
			String cart, String instructions,
			String box, String comment, int listType) 
	throws SQLException {
		boolean isAddPossible = connector.verifyBaseGame(title, system);
		
		if(isAddPossible) {
			connector.addPersonalGame(title, system, cart, instructions,
					box, comment, listType);
		}
	}
	
	/**
	 * Called to edit an existing game in the database.<br>
	 * Verifies if the game is changing from one table to another,
	 * or if it staying at the same position and only some columns are
	 * being edited.
	 * <br>
	 * 
	 * @param
	 * request Reference variable to an {@link EditGameRequest},
	 * containing all the values of the game.
	 * <br>
	 * 
	 * @throws
	 * SQLException
	 * */
	private void editGame(EditGameRequest request) throws SQLException {
		// Check if the game might have to switch tables in the database.
		if(request.hasListTypeChanged()) {
			connector.deletePersonalGame(
					request.getId(),
					request.getPreviousListType());
			addGame(request.toAddGameRequest());
		}
		else {
			connector.editGame(
					request.getId(), request.getTitle(), request.getCart(),
					request.getInstructions(), request.getBox(),
					request.getComment(), request.getListType());
		}
	}
	
	/**
	 * Helper method for converting a boolean value to an integer
	 * value.<br>
	 * This is done to satisfy MySQL's version of booleans (TINYINT)<br>
	 * true = 1, false = 0
	 * <br>
	 * 
	 * @param
	 * b The boolean value to be converted.<br>
	 * 
	 * @return
	 * An integer value representing the boolean value.
	 * */
	private int convertBooleanToInt(boolean b) {
		return b ? 1 : 0;
	}
}
